// 功能作用原理（面向新手）：
// 本文件定义了 3 个装饰器（@CheckPolicies、@Can、@Connot），用于将“权限检查规则”写入到路由处理器的元数据中，
// 由守卫（CaslGuard）在请求到来时读取并调用 CASL 的 Ability（能力）进行判断。
// 关键点：
// 1) SetMetadata 会把你传入的策略或能力检查函数，存到当前路由方法/控制器类的“元数据”中。
// 2) 守卫通过 Reflector 读取这些元数据，拿到 ability（AnyMongoAbility），执行 can/cannot 或你自定义的处理函数，最终决定是否通过。
// 3) “Action（操作枚举）+ Subject（资源类型）+ Conditions（条件）”组成标准权限表达，避免使用字符串造成拼写错误和分散管理。
// 4) 不改变任何业务逻辑，仅将“权限判断方式”与路由声明进行解耦，提升代码可读性与维护性。
// 使用示例（控制器中）：
//   @UseGuards(CaslGuard)
//   @Can(Action.Read, 'Post', { authorId: user.id })
//   @CheckPolicies((ability) => ability.can(Action.Manage, 'Post')) // 可叠加多条策略
//   getPost() { /* ... */ }
//
// 数据流（从声明到执行）：
//   Controller 路由方法上声明装饰器 -> SetMetadata 写入元数据 -> 守卫(CaslGuard)在 canActivate 中读取元数据 ->
//   构建 ability -> 依次执行策略/能力检查 -> 返回允许/拒绝。
//
// 术语简释：
// - Ability：CASL 的“能力对象”，描述用户在系统中对不同资源的允许/禁止操作。
// - Action：统一的操作枚举，如 Create、Read、Update、Delete、Manage。
// - Subject：被操作的资源（实体/模型），InferSubjects 提供类型推导。
// - Conditions：可选条件，细化规则（如“只能修改自己创建的文章”）。
import { AnyMongoAbility, InferSubjects } from "@casl/ability";
import { SetMetadata } from "@nestjs/common";
import { Action } from "../enum/action.enum";

export enum CHECK_POLICIES_KEY {
  // 路由元数据键名：守卫通过它们区分不同类型的权限检查
  HANDLER = "CHECK_POLICIES_HANDLER", // 存放策略处理回调集合（形如 ability => boolean）
  CAN = "CHECK_POLICIES_CAN", // 存放能力允许检查（ability.can(...)）
  CANNOT = "CHECK_POLICIES_CANNOT", // 存放能力禁止检查（ability.cannot(...)）
}

// 策略处理回调类型：接收 CASL Ability，返回布尔值表示是否通过
export type PolicyHandlerCallBack = (ability: AnyMongoAbility) => boolean;

export type CaslHandlerType = PolicyHandlerCallBack | PolicyHandlerCallBack[];

// @CheckPolicies：把一组“自定义策略处理函数”写入元数据，守卫会依次执行它们。
// 适合表达更灵活/复杂的逻辑（不仅限于 can/cannot）。
export const CheckPolicies = (...handlers: PolicyHandlerCallBack[]) =>
  SetMetadata(CHECK_POLICIES_KEY.HANDLER, handlers);

// @Can：声明“允许”的能力检查
// 参数：
// - action: 使用统一的 Action 枚举，避免字符串拼写错误和风格不一致
// - subject: 使用 InferSubjects<T> 提供强类型资源描述（如 'User'、'Post' 或具体类）
// - conditions: 可选条件（如 { authorId: currentUser.id }），实现细粒度控制
export const Can = (action: Action, subject: InferSubjects<any>, conditions?: any) =>
  SetMetadata(CHECK_POLICIES_KEY.CAN, (ability: AnyMongoAbility) => ability.can(action, subject, conditions));

// @Connot：声明“禁止”的能力检查（与 ability.cannot(...) 对应）
// 使用场景：明确表达某些操作在特定条件下不被允许（辅助可读性或配合更复杂策略）。
// 注意：保持现有导出名称 Connot，不做重命名。
// 参数同 @Can。
export const Connot = (action: Action, subject: InferSubjects<any>, conditions?: any) =>
  SetMetadata(CHECK_POLICIES_KEY.CANNOT, (ability: AnyMongoAbility) => ability.cannot(action, subject, conditions));
